package com.sibu.yx.promotion.admin.web.support.quartz;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import org.quartz.JobDetail;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean;

import com.doubo.common.util.StringUtil;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

/**
 * @author suzheng
 * @date 2020-07-08
 **/
@Slf4j
public class LocalSchedulerFactoryBeanCustomizer implements BeanPostProcessor, BeanFactoryAware, SchedulerFactoryBeanCustomizer {
    private BeanFactory beanFactory;

    private List<String> beanNames = new ArrayList<>(5);

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (AnnotationUtils.findAnnotation(bean.getClass(), CustomQuartzJob.class)!=null) {
            this.beanNames.add(beanName);
        }
        return bean;
    }

    @SneakyThrows
    @Override
    public void customize(SchedulerFactoryBean schedulerFactoryBean) {
        Set<Object> jobs = beanNames.stream().map(beanName -> this.beanFactory.getBean(beanName)).collect(Collectors.toSet());
        schedulerFactoryBean.setTriggers(scanAllTriggers(jobs));
    }

    private Trigger[] scanAllTriggers(Set<Object> jobs) throws Exception {
        List<Trigger> tgList = new ArrayList<>();
        try {
            for (Object ossJob : jobs) {
                Method[] methods = ossJob.getClass().getMethods();
                for (Method method : methods) {
                    CustomScheduled sch = method.getAnnotation(CustomScheduled.class);
                    if (sch == null) {
                        continue;
                    }
                    if (method.getParameterTypes().length > 0) {
                        throw new SchedulerException(ossJob.getClass() + " method[" + method.getName() + "] could not have any Parameters");
                    }
                    if (!"void".equals(method.getReturnType().toString())) {
                        throw new SchedulerException(ossJob.getClass() + " method[" + method.getName() + "] 'returnType must be void");
                    }

                    //查找destroy方法
                    String destroyMethodName = sch.destroyMethod();
                    if (StringUtil.isNotBlank(destroyMethodName)) {
                        Method destroy = null;
                        for (Method destroyMethod : methods) {
                            if (destroyMethod.getName().equals(destroyMethodName)) {
                                if (destroyMethod.getParameterTypes().length > 0) {
                                    throw new SchedulerException(ossJob.getClass() + " destroy method[" + destroyMethodName + "] could not have any Parameters");
                                }
                                if (!"void".equals(destroyMethod.getReturnType().toString())) {
                                    throw new SchedulerException(ossJob.getClass() + " destroy method[" + destroyMethodName + "] 'returnType must be void");
                                }
                                destroy = destroyMethod;
                                break;
                            }
                        }
                        if (destroy == null) {
                            throw new SchedulerException(ossJob.getClass() + " destroy method[" + destroyMethodName + "] is not exist!");
                        }
                    }

                    MethodInvokingJobDetailFactoryBean jobDetail = createJobDetail(ossJob, method);
                    jobDetail.setConcurrent(sch.concurrent());
                    tgList.add(createTrigger(sch, jobDetail.getObject(), ossJob, method));
                }
            }
        } catch (Exception ex) {
            log.error("定时器初始化错误:" + ex.getMessage());
            throw ex;
        }
        Trigger[] ts = new Trigger[tgList.size()];
        return tgList.toArray(ts);
    }

    private MethodInvokingJobDetailFactoryBean createJobDetail(Object obj, Method method) throws Exception {
        MethodInvokingJobDetailFactoryBean jobDetail = new MethodInvokingJobDetailFactoryBean();
        jobDetail.setTargetObject(obj);
        jobDetail.setTargetMethod(method.getName());
        String name = method.getName() + "JobDetail" + obj.getClass().getSimpleName();
        jobDetail.setName(name);
        jobDetail.afterPropertiesSet();
        return jobDetail;
    }

    private Trigger createTrigger(CustomScheduled scheduled, JobDetail jobDetail,
                                  Object ossJob, Method method) throws Exception {
        Trigger trigger;
        String triggerName = "trigger" + ossJob.getClass().getSimpleName() + method.getName();
        if (scheduled.cron().isEmpty()) {
            SimpleTriggerFactoryBean simpleTrigger = new SimpleTriggerFactoryBean();
            simpleTrigger.setName(triggerName);
            simpleTrigger.setStartDelay(scheduled.startDelay());
            simpleTrigger.setRepeatInterval(scheduled.repeatInterval());
            simpleTrigger.setJobDetail(jobDetail);
            simpleTrigger.afterPropertiesSet();
            trigger = simpleTrigger.getObject();
        } else {
            CronTriggerFactoryBean cronTrigger = new CronTriggerFactoryBean();
            cronTrigger.setName(triggerName);
            cronTrigger.setStartDelay(scheduled.startDelay());
            cronTrigger.setCronExpression(scheduled.cron());
            cronTrigger.setJobDetail(jobDetail);
            cronTrigger.afterPropertiesSet();
            trigger = cronTrigger.getObject();
        }
        return trigger;
    }


    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
}